package com.cscec5b.inspection.config;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 统一记录 HTTP 请求/响应日志（含 body、状态码、耗时）
 * - 自动给每个请求生成 traceId（也可从上游透传）
 * - 默认脱敏 token/password 等敏感字段
 * - 大响应/文件下载/静态资源自动跳过或只记摘要
 */
@Component
@Order(10) // 尽量靠前
public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    private static final Logger log = LoggerFactory.getLogger(RequestResponseLoggingFilter.class);

    // 最大打印的 body 长度，超出会截断
    @Value("${app.http-log.max-payload:4096}")
    private int maxPayload;

    // 是否打印响应体
    @Value("${app.http-log.include-response:true}")
    private boolean includeResponse;

    // 跳过的路径前缀（静态资源/健康检查等）
    private static final String[] SKIP_PATHS = { "/actuator", "/favicon", "/static", "/assets", "/webjars" };

    // 不打印的敏感头
    private static final Set<String> SENSITIVE_HEADERS = Set.of("authorization", "cookie", "set-cookie");

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        String uri = request.getRequestURI();
        for (String p : SKIP_PATHS) if (uri.startsWith(p)) return true;
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest rawReq, HttpServletResponse rawRes, FilterChain chain)
            throws ServletException, IOException {

        // 包装一下，才能在不消耗流的情况下读取 body
        ContentCachingRequestWrapper req = new ContentCachingRequestWrapper(rawReq);
        ContentCachingResponseWrapper res = new ContentCachingResponseWrapper(rawRes);

        // traceId（可从上游透传 X-Request-Id / X-B3-TraceId，这里简单生成）
        String traceId = Optional.ofNullable(MDC.get("traceId"))
                .orElseGet(() -> {
                    String id = UUID.randomUUID().toString().replace("-", "").substring(0, 12);
                    MDC.put("traceId", id);
                    return id;
                });

        long start = System.currentTimeMillis();
        try {
            chain.doFilter(req, res);
        } finally {
            long cost = System.currentTimeMillis() - start;

            // ------ 请求信息 ------
            String uri = req.getRequestURI() + (req.getQueryString() != null ? "?" + req.getQueryString() : "");
            String method = req.getMethod();
            String ip = Optional.ofNullable(req.getHeader("X-Forwarded-For")).orElse(req.getRemoteAddr());

            String headers = Collections.list(req.getHeaderNames()).stream()
                    .filter(h -> !SENSITIVE_HEADERS.contains(h.toLowerCase()))
                    .map(h -> h + "=" + Collections.list(req.getHeaders(h)))
                    .collect(Collectors.joining(", "));

            String reqBody = toBody(req.getContentAsByteArray(), req.getCharacterEncoding());
            reqBody = mask(reqBody);
            reqBody = truncate(reqBody, maxPayload);

            // ------ 响应信息 ------
            int status = res.getStatus();
            String resBody = "";
            if (includeResponse && isTextLike(res.getContentType())) {
                resBody = toBody(res.getContentAsByteArray(), res.getCharacterEncoding());
                resBody = mask(resBody);
                resBody = truncate(resBody, maxPayload);
            } else if (includeResponse) {
                resBody = "[non-text or large payload]";
            }

            // 打印
            log.info("traceId={} {} {} -> status={} cost={}ms ip={} headers=[{}] reqBody={} resBody={}",
                    traceId, method, uri, status, cost, ip, headers, reqBody, resBody);

            // IMPORTANT: 把响应体回写给客户端
            res.copyBodyToResponse();

            MDC.remove("traceId");
        }
    }

    private static String toBody(byte[] buf, String charset) {
        if (buf == null || buf.length == 0) return "";
        try {
            return new String(buf, charset != null ? charset : StandardCharsets.UTF_8.name());
        } catch (Exception e) {
            return "[binary " + buf.length + " bytes]";
        }
    }

    private static boolean isTextLike(String contentType) {
        if (contentType == null) return false;
        String ct = contentType.toLowerCase(Locale.ROOT);
        return ct.contains("json") || ct.contains("xml") || ct.contains("text") || ct.contains("javascript");
    }

    // 简单脱敏：password/token/bearer 等
    private static String mask(String s) {
        if (s == null) return null;
        return s
                .replaceAll("(?i)\"password\"\\s*:\\s*\".*?\"", "\"password\":\"***\"")
                .replaceAll("(?i)\"token\"\\s*:\\s*\".*?\"", "\"token\":\"***\"")
                .replaceAll("(?i)Bearer\\s+[A-Za-z0-9\\-_.]+", "Bearer ***");
    }

    private static String truncate(String s, int len) {
        if (s == null) return null;
        return s.length() > len ? s.substring(0, len) + "...(truncated)" : s;
    }
}
